﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Tasks;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System.Net.Http;

using VideoPlay.PhoneClient.ViewModels;

using MyToolkit.Multimedia;
using System.Text;
using System.Text.RegularExpressions;

namespace VideoPlay.PhoneClient.Views
{
    public partial class PlayVideoView : PhoneApplicationPage
    {
        private string videoId;

        public PlayVideoView()
        {
            InitializeComponent();

            player.PlayerStateChanged+=player_PlayerStateChanged;
            this.LoadingLabel.Visibility = System.Windows.Visibility.Visible;

            PlayVideoViewModel vm = DataContext as PlayVideoViewModel;
            this.videoId = vm.VideoUrl;

            Play();
        }

        void player_PlayerStateChanged(object sender, RoutedPropertyChangedEventArgs<Microsoft.PlayerFramework.PlayerState> e)
        {
            if (e.NewValue == Microsoft.PlayerFramework.PlayerState.Loading)
            {
                this.LoadingLabel.Visibility = System.Windows.Visibility.Visible;
            }
            else if (e.NewValue == Microsoft.PlayerFramework.PlayerState.Started)
            {
                this.LoadingLabel.Visibility = System.Windows.Visibility.Collapsed;
            }
        }

        private string GetVideoUrl(string url)
        {
            if (url.Contains("youtube.com"))
            {
                string[] array = url.Split(new char[] { '=' });
                return array[1];
            }

            return url;
        }

        private string GetYoutubeId(string url)
        {
            string[] array = url.Split(new char[] { '=' });
            return array[1];
        }

        private string GetMp4Link(string url)
        {
            string mp4Link = url.Substring(0, url.IndexOf("mp4") + "mp4".Length);
            return mp4Link;
        }

        private async void Play()
        {
            try
            {
                if (!videoId.Contains("youtube.com"))
                {
                    player.Source = new Uri(GetMp4Link(videoId));
                }
                else
                {
                    var url = await YouTube.GetVideoUriAsync(GetYoutubeId(videoId), YouTubeQuality.QualityLow);
                    if (url != null)
                    {
                        player.Source = url.Uri;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Ops!!! Cannot play the video. Details: " + ex.Message);
            }

            //await Windows.System.Launcher.LaunchUriAsync(
            //    new System.Uri("vnd.youtube:Eib7PcOFpxA")
            //);

            //MediaPlayerLauncher mediaPlayerLauncher = new MediaPlayerLauncher();
            //mediaPlayerLauncher.Media = new Uri(string.Format("https://www.youtube.com/watch?v={0}", this.videoId), UriKind.Absolute);


            //mediaPlayerLauncher.Show();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            MsAd.Visibility = System.Windows.Visibility.Collapsed;
        }

        //public void GetVideoUrl(string avideoId)
        //{
        //    string VideoRequest = "http://www.youtube.com/get_video_info?&video_id=" + avideoId + "&el=detailpage&ps=default&eurl=&gl=US&hl=en";
        //    // string VideoRequest = "http://www.youtube.com/get_video_info?video_id=" + avideoId;
        //    WebClient xmlClient = new WebClient();
        //    xmlClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(DownloadStringCompleted_GetVideoUrl);
        //    xmlClient.DownloadStringAsync(new Uri(VideoRequest, UriKind.RelativeOrAbsolute));
        //}

    }

    public class YouTubeUri
    {
        internal string _url;

        public int Itag { get; internal set; }
        public Uri Uri { get { return new Uri(_url, UriKind.Absolute); } }
        public string Type { get; internal set; }

        public bool HasAudio
        {
            get
            {
                return AudioQuality != YouTubeQuality.Unknown && AudioQuality != YouTubeQuality.NotAvailable;
            }
        }

        public bool HasVideo
        {
            get
            {
                return VideoQuality != YouTubeQuality.Unknown && VideoQuality != YouTubeQuality.NotAvailable;
            }
        }

        public bool Is3DVideo
        {
            get
            {
                if (VideoQuality == YouTubeQuality.Unknown)
                    return false;
                return Itag >= 82 && Itag <= 85;
            }
        }

        public YouTubeQuality VideoQuality
        {
            get
            {
                switch (Itag)
                {
                    // video & audio
                    case 5: return YouTubeQuality.Quality240P;
                    case 6: return YouTubeQuality.Quality270P;
                    case 17: return YouTubeQuality.Quality144P;
                    case 18: return YouTubeQuality.Quality360P;
                    case 22: return YouTubeQuality.Quality720P;
                    case 34: return YouTubeQuality.Quality360P;
                    case 35: return YouTubeQuality.Quality480P;
                    case 36: return YouTubeQuality.Quality240P;
                    case 37: return YouTubeQuality.Quality1080P;
                    case 38: return YouTubeQuality.Quality3072P;

                    // 3d video & audio
                    case 82: return YouTubeQuality.Quality360P;
                    case 83: return YouTubeQuality.Quality480P;
                    case 84: return YouTubeQuality.Quality720P;
                    case 85: return YouTubeQuality.Quality520P;

                    // video only
                    case 133: return YouTubeQuality.Quality240P;
                    case 134: return YouTubeQuality.Quality360P;
                    case 135: return YouTubeQuality.Quality480P;
                    case 136: return YouTubeQuality.Quality720P;
                    case 137: return YouTubeQuality.Quality1080P;
                    case 160: return YouTubeQuality.Quality144P;

                    // audio only
                    case 139: return YouTubeQuality.NotAvailable;
                    case 140: return YouTubeQuality.NotAvailable;
                    case 141: return YouTubeQuality.NotAvailable;
                }
                return YouTubeQuality.Unknown;
            }
        }

        public YouTubeQuality AudioQuality
        {
            get
            {
                switch (Itag)
                {
                    // video & audio
                    case 5: return YouTubeQuality.QualityLow;
                    case 6: return YouTubeQuality.QualityLow;
                    case 17: return YouTubeQuality.QualityLow;
                    case 18: return YouTubeQuality.QualityMedium;
                    case 22: return YouTubeQuality.QualityHigh;
                    case 34: return YouTubeQuality.QualityMedium;
                    case 35: return YouTubeQuality.QualityMedium;
                    case 36: return YouTubeQuality.QualityLow;
                    case 37: return YouTubeQuality.QualityHigh;
                    case 38: return YouTubeQuality.QualityHigh;

                    // 3d video & audio
                    case 82: return YouTubeQuality.QualityMedium;
                    case 83: return YouTubeQuality.QualityMedium;
                    case 84: return YouTubeQuality.QualityHigh;
                    case 85: return YouTubeQuality.QualityHigh;

                    // video only
                    case 133: return YouTubeQuality.NotAvailable;
                    case 134: return YouTubeQuality.NotAvailable;
                    case 135: return YouTubeQuality.NotAvailable;
                    case 136: return YouTubeQuality.NotAvailable;
                    case 137: return YouTubeQuality.NotAvailable;
                    case 160: return YouTubeQuality.NotAvailable;

                    // audio only
                    case 139: return YouTubeQuality.QualityLow;
                    case 140: return YouTubeQuality.QualityMedium;
                    case 141: return YouTubeQuality.QualityHigh;
                }
                return YouTubeQuality.Unknown;
            }
        }

        internal bool IsValid
        {
            get { return _url != null && Itag > 0 && Type != null; }
        }
    }

    public static class YouTube
    {
        public const YouTubeQuality DefaultMinQuality = YouTubeQuality.Quality144P;

        private const string BotUserAgent = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)";

#if WP7 || SL5

        /// <summary>
        /// Returns the title of the YouTube video. 
        /// </summary>
        public static Task<string> GetVideoTitleAsync(string youTubeId) // should be improved
        {
            var source = new TaskCompletionSource<string>();
            var web = new WebClient();
            web.OpenReadCompleted += (sender, args) =>
            {
                if (args.Error != null)
                    source.SetException(args.Error);
                else if (args.Cancelled)
                    source.SetCanceled();
                else
                {
                    string result = null;
                    var bytes = args.Result.ReadToEnd();
                    var html = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
                    var startIndex = html.IndexOf(" title=\"");
                    if (startIndex != -1)
                    {
                        startIndex = html.IndexOf(" title=\"", startIndex + 1);
                        if (startIndex != -1)
                        {
                            startIndex += 8;
                            var endIndex = html.IndexOf("\">", startIndex);
                            if (endIndex != -1)
                                result = html.Substring(startIndex, endIndex - startIndex);
                        }
                    }
                    source.SetResult(result);
                }
            };
            web.Headers[HttpRequestHeader.UserAgent] = BotUserAgent;
            web.OpenReadAsync(new Uri("http://www.youtube.com/watch?v=" + youTubeId + "&nomobile=1"));
            return source.Task;
        }

#elif WP8 || WINRT

		/// <summary>
        /// Returns the title of the YouTube video. 
        /// </summary>
        public static Task<string> GetVideoTitleAsync(string youTubeId)
        {
            return GetVideoTitleAsync(youTubeId, CancellationToken.None);
        }

        /// <summary>
        /// Returns the title of the YouTube video. 
        /// </summary>
        public static async Task<string> GetVideoTitleAsync(string youTubeId, CancellationToken token)
        {
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Add("User-Agent", BotUserAgent);
                var response = await client.GetAsync("http://www.youtube.com/watch?v=" + youTubeId + "&nomobile=1", token);
                var html = await response.Content.ReadAsStringAsync();
                var startIndex = html.IndexOf(" title=\"");
                if (startIndex != -1)
                {
                    startIndex = html.IndexOf(" title=\"", startIndex + 1);
                    if (startIndex != -1)
                    {
                        startIndex += 8;
                        var endIndex = html.IndexOf("\">", startIndex);
                        if (endIndex != -1)
                            return html.Substring(startIndex, endIndex - startIndex);
                    }
                }
                return null;
            }
        }

#endif

        /// <summary>
        /// Returns a thumbnail for the given YouTube ID. 
        /// </summary>
        public static Uri GetThumbnailUri(string youTubeId, YouTubeThumbnailSize size = YouTubeThumbnailSize.Medium)
        {
            switch (size)
            {
                case YouTubeThumbnailSize.Small:
                    return new Uri("http://img.youtube.com/vi/" + youTubeId + "/default.jpg", UriKind.Absolute);
                case YouTubeThumbnailSize.Medium:
                    return new Uri("http://img.youtube.com/vi/" + youTubeId + "/hqdefault.jpg", UriKind.Absolute);
                case YouTubeThumbnailSize.Large:
                    return new Uri("http://img.youtube.com/vi/" + youTubeId + "/maxresdefault.jpg", UriKind.Absolute);
            }
            throw new Exception();
        }

        /// <summary>
        /// Returns the best matching YouTube stream uri. The method returns null if no uri has been found.
        /// </summary>
        public static YouTubeUri GetBestVideoUri(IEnumerable<YouTubeUri> uris, YouTubeQuality minQuality, YouTubeQuality maxQuality)
        {
            return uris
                .Where(u => u.HasVideo && u.HasAudio && !u.Is3DVideo && u.VideoQuality >= minQuality && u.VideoQuality <= maxQuality)
                .OrderByDescending(u => u.Itag)
                .FirstOrDefault();
        }

        /// <summary>
        /// Returns the best matching YouTube stream URI. 
        /// </summary>
        /// <returns>Returns null if no URI has been found. </returns>
        public static Task<YouTubeUri> GetVideoUriAsync(string youTubeId, YouTubeQuality maxQuality)
        {
            return GetVideoUriAsync(youTubeId, DefaultMinQuality, maxQuality, CancellationToken.None);
        }

        /// <summary>
        /// Returns the best matching YouTube stream URI. 
        /// </summary>
        /// <returns>Returns null if no URI has been found. </returns>
        public static Task<YouTubeUri> GetVideoUriAsync(string youTubeId, YouTubeQuality maxQuality, CancellationToken token)
        {
            return GetVideoUriAsync(youTubeId, DefaultMinQuality, maxQuality, token);
        }

        /// <summary>
        /// Returns the best matching YouTube stream URI. 
        /// </summary>
        /// <returns>Returns null if no URI has been found. </returns>
        public static Task<YouTubeUri> GetVideoUriAsync(string youTubeId, YouTubeQuality minQuality, YouTubeQuality maxQuality)
        {
            return GetVideoUriAsync(youTubeId, minQuality, maxQuality, CancellationToken.None);
        }

        /// <summary>
        /// Returns the best matching YouTube stream URI. 
        /// </summary>
        /// <returns>Returns null if no URI has been found. </returns>
        public static async Task<YouTubeUri> GetVideoUriAsync(string youTubeId, YouTubeQuality minQuality, YouTubeQuality maxQuality, CancellationToken token)
        {
            var uris = await GetUrisAsync(youTubeId, token);
            return GetBestVideoUri(uris, minQuality, maxQuality);
        }

        /// <summary>
        /// Returns all available URIs for the given YouTube ID. 
        /// </summary>
        public static Task<YouTubeUri[]> GetUrisAsync(string youTubeId)
        {
            return GetUrisAsync(youTubeId, CancellationToken.None);
        }

        /// <summary>
        /// Returns all available URIs for the given YouTube ID. 
        /// </summary>
        public static async Task<YouTubeUri[]> GetUrisAsync(string youTubeId, CancellationToken token)
        {
            var urls = new List<YouTubeUri>();
            string javaScriptCode = null;

            var response = await HttpGet("http://www.youtube.com/watch?v=" + youTubeId + "&nomobile=1");
            var match = Regex.Match(response, "url_encoded_fmt_stream_map\": \"(.*?)\"");
            var data = Uri.UnescapeDataString(match.Groups[1].Value);
            match = Regex.Match(response, "adaptive_fmts\": \"(.*?)\"");
            var data2 = Uri.UnescapeDataString(match.Groups[1].Value);

            var arr = Regex.Split(data + "," + data2, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); // split by comma but outside quotes
            foreach (var d in arr)
            {
                var url = "";
                var signature = "";
                var tuple = new YouTubeUri();
                foreach (var p in d.Replace("\\u0026", "\t").Split('\t'))
                {
                    var index = p.IndexOf('=');
                    if (index != -1 && index < p.Length)
                    {
                        try
                        {
                            var key = p.Substring(0, index);
                            var value = Uri.UnescapeDataString(p.Substring(index + 1));
                            if (key == "url")
                                url = value;
                            else if (key == "itag")
                                tuple.Itag = int.Parse(value);
                            else if (key == "type" && (value.Contains("video/mp4") || value.Contains("audio/mp4")))
                                tuple.Type = value;
                            else if (key == "sig")
                                signature = value;
                            else if (key == "s")
                            {
                                if (javaScriptCode == null)
                                {
                                    var javaScriptUri = "http://s.ytimg.com/yts/jsbin/html5player-" +
                                        Regex.Match(response, "\"\\\\/\\\\/s.ytimg.com\\\\/yts\\\\/jsbin\\\\/html5player-(.+?)\\.js\"").Groups[1] + ".js";
                                    javaScriptCode = await HttpGet(javaScriptUri);
                                }

                                signature = GenerateSignature(value, javaScriptCode);
                            }
                        }
                        catch { }
                    }
                }

                if (url.Contains("&signature="))
                    tuple._url = url;
                else
                    tuple._url = url + "&signature=" + signature;

                if (tuple.IsValid)
                    urls.Add(tuple);
            }

            return urls.ToArray();
        }

        private static string GenerateSignature(string s, string javaScriptCode)
        {
            var functionName = Regex.Match(javaScriptCode, "signature=(\\w+)\\(\\w+\\)").Groups[1].ToString();
            var functionMath = Regex.Match(javaScriptCode, "function " + functionName + "\\((\\w+)\\)\\{(.+?)\\}", RegexOptions.Singleline);

            var parameterName = functionMath.Groups[1].ToString();
            var functionBody = functionMath.Groups[2].ToString();

            foreach (var line in functionBody.Split(';').Select(l => l.Trim()))
            {
                if (Regex.IsMatch(line, parameterName + "=" + parameterName + "\\.reverse\\(\\)"))
                {
                    var charArray = s.ToCharArray();
                    Array.Reverse(charArray);
                    s = new string(charArray);
                }
                else if (Regex.IsMatch(line, parameterName + "=" + parameterName + "\\.slice\\(\\d+\\)"))
                    s = Slice(s, Convert.ToInt32(Regex.Match(line, parameterName + "=" + parameterName + "\\.slice\\((\\d+)\\)").Groups[1].ToString()));
                else if (Regex.IsMatch(line, parameterName + "=\\w+\\(" + parameterName + ",\\d+\\)"))
                    s = Swap(s, Convert.ToInt32(Regex.Match(line, parameterName + "=\\w+\\(" + parameterName + ",(\\d+)\\)").Groups[1].ToString()));
                else if (Regex.IsMatch(line, parameterName + "\\[0\\]=" + parameterName + "\\[\\d+%" + parameterName + "\\.length\\]"))
                    s = Swap(s, Convert.ToInt32(Regex.Match(line, parameterName + "\\[0\\]=" + parameterName + "\\[(\\d+)%" + parameterName + "\\.length\\]").Groups[1].ToString()));
            }
            return s;
        }

        private static string Slice(string input, int length)
        {
            return input.Substring(length);
        }

        private static string Swap(string input, int position)
        {
            var str = new StringBuilder(input);
            var swapChar = str[position];
            str[position] = str[0];
            str[0] = swapChar;
            return str.ToString();
        }

#if WINRT

        private static async Task<string> HttpGet(string uri)
        {
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Add("User-Agent", BotUserAgent);
                var response = await client.GetAsync(uri);
                return await response.Content.ReadAsStringAsync();
            }
        }

#else

        private static Task<string> HttpGet(string uri)
        {
            var task = new TaskCompletionSource<string>();

            var web = new WebClient();
            web.OpenReadCompleted += (sender, args) =>
            {
                if (args.Cancelled)
                    task.SetCanceled();
                else if (args.Error != null)
                    task.SetException(args.Error);
                else
                {
                    //var bytes = args.Result.ReadToEnd();

                    byte[] bytes = new byte[] { };
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        args.Result.CopyTo(memoryStream);
                        bytes = memoryStream.ToArray();

                        task.SetResult(Encoding.UTF8.GetString(bytes, 0, bytes.Length));
                    }
                }
            };

            web.Headers["User-Agent"] = BotUserAgent;
            web.OpenReadAsync(new Uri(uri));

            return task.Task;
        }

#endif

        #region Phone

#if WP7 || WP8


        /// <summary>
        /// Plays the YouTube video in the Windows Phone's external player. 
        /// </summary>
        /// <param name="youTubeId">The YouTube ID</param>
        /// <param name="maxQuality">The maximum allowed video quality. </param>
        /// <returns>Awaitable task. </returns>
        public static Task PlayAsync(string youTubeId, YouTubeQuality maxQuality)
        {
            return PlayAsync(youTubeId, maxQuality, CancellationToken.None);
        }

        /// <summary>
        /// Plays the YouTube video in the Windows Phone's external player. 
        /// </summary>
        /// <param name="youTubeId">The YouTube ID</param>
        /// <param name="maxQuality">The maximum allowed video quality. </param>
        /// <param name="token">The cancellation token</param>
        /// <returns>Awaitable task. </returns>
        public static async Task PlayAsync(string youTubeId, YouTubeQuality maxQuality, CancellationToken token)
        {
            var uri = await GetVideoUriAsync(youTubeId, maxQuality, token);
            if (uri != null)
            {
                var launcher = new MediaPlayerLauncher
                {
                    Controls = MediaPlaybackControls.All,
                    Media = uri.Uri
                };
                launcher.Show();
            }
            else
                throw new Exception("no_video_urls_found");
        }

        private static CancellationTokenSource _cancellationToken;
        private static PageDeactivator _oldState;

        /// <summary>
        /// Plays the YouTube video in the Windows Phone's external player. 
        /// Disables the current page and shows a progress indicator until 
        /// the YouTube movie URI has been loaded and the video playback starts.
        /// </summary>
        /// <param name="youTubeId">The YouTube ID</param>
        /// <param name="manualActivatePage">if true add YouTube.CancelPlay() in OnNavigatedTo() of the page (better)</param>
        /// <param name="maxQuality">The maximum allowed video quality. </param>
        /// <returns>Awaitable task. </returns>
        public static async Task PlayWithPageDeactivationAsync(string youTubeId, bool manualActivatePage, YouTubeQuality maxQuality)
        {
            PhoneApplicationPage page;

            lock (typeof(YouTube))
            {
                if (_oldState != null)
                    return;

                if (SystemTray.ProgressIndicator == null)
                    SystemTray.ProgressIndicator = new ProgressIndicator();

                SystemTray.ProgressIndicator.IsVisible = true;
                SystemTray.ProgressIndicator.IsIndeterminate = true;

                page = Environment.PhoneApplication.CurrentPage;

                _oldState = PageDeactivator.Inactivate();
                _cancellationToken = new CancellationTokenSource();
            }

            try
            {
                await PlayAsync(youTubeId, maxQuality, _cancellationToken.Token);
                if (page == Environment.PhoneApplication.CurrentPage)
                    CancelPlay(manualActivatePage);
            }
            catch (Exception)
            {
                if (page == Environment.PhoneApplication.CurrentPage)
                    CancelPlay(false);
                throw;
            }
        }

        /// <summary>
        /// Call this method in OnBackKeyPress() or in OnNavigatedTo() when 
        /// using PlayWithDeactivationAsync() and manualActivatePage = true like this 
        /// e.Cancel = YouTube.CancelPlay();
        /// 
        /// </summary>
        /// <returns></returns>
        public static bool CancelPlay()
        {
            return CancelPlay(false);
        }
        
        /// <summary>
        /// Should be called when using PlayWithDeactivationAsync() when the back key has been pressed. 
        /// </summary>
        /// <param name="args"></param>
        public static void HandleBackKeyPress(CancelEventArgs args)
        {
            if (CancelPlay())
                args.Cancel = true;
        }

        private static bool CancelPlay(bool manualActivatePage)
        {
            lock (typeof(YouTube))
            {
                if (_oldState == null && _cancellationToken == null)
                    return false;

                if (_cancellationToken != null)
                {
                    _cancellationToken.Cancel();
                    _cancellationToken.Dispose();
                    _cancellationToken = null;
                }

                if (!manualActivatePage && _oldState != null)
                {
                    _oldState.Revert();
                    SystemTray.ProgressIndicator.IsVisible = false;
                    _oldState = null;
                }

                return true;
            }
        }

        /// <summary>
        /// Obsolete: Use PlayAsync() instead
        /// </summary>
        [Obsolete]
        public static async void Play(string youTubeId, YouTubeQuality maxQuality, Action<Exception> completed)
        {
            try
            {
                await PlayAsync(youTubeId, maxQuality);
                if (completed != null)
                    completed(null);
            }
            catch (Exception exception)
            {
                if (completed != null)
                    completed(exception);
            }
        }

        /// <summary>
        /// Obsolete: Use PlayWithPageDeactivationAsync() instead
        /// </summary>
        [Obsolete]
        public static async void Play(string youTubeId, bool manualActivatePage, YouTubeQuality maxQuality, Action<Exception> completed)
        {
            try
            {
                await PlayWithPageDeactivationAsync(youTubeId, manualActivatePage, maxQuality);
                if (completed != null)
                    completed(null);
            }
            catch (Exception exception)
            {
                if (completed != null)
                    completed(exception);
            }
        }

#endif
        #endregion
    }
}